/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pipeline/pipeline_data_structures.h"

#include <cassert>
#include <vector>

namespace NAMESPACE{

// ****** PipeStep implementation **********************************************

PipeStep::~PipeStep(){
    delete inputs;
    delete outputs;
    delete criteria;

    delete input_generators;
    delete criteria_generators;
    delete output_generators;

    delete output_formatters;
}
PipeStep::PipeStep() : inputs(new std::map<IOKey, input_t>()),
                       criteria(new std::map<IOKey, output_t>()),
                       outputs(new std::map<IOKey, output_t>()),
                       input_generators(new std::list<Generator*>()),
                       criteria_generators(new std::list<Generator*>()),
                       output_generators(new std::list<Generator*>()),
                       output_formatters(new std::list<Formatter*>()),
                       computation(0){}

PipeStep::PipeStep(const PipeStep& ps) : inputs(ps.inputs ? new std::map<IOKey, input_t>(*(ps.inputs)) : 0),
                                         criteria(ps.criteria ? new std::map<IOKey, criteria_t>(*(ps.criteria)) : 0),
                                         outputs(ps.outputs ? new std::map<IOKey, output_t>(*(ps.outputs)) : 0),
                                         input_generators(ps.input_generators ? new std::list<Generator*>(*(ps.input_generators)) : 0),
                                         criteria_generators(ps.criteria_generators  ? new std::list<Generator*>(*(ps.criteria_generators)) : 0),
                                         output_generators(ps.output_generators ? new std::list<Generator*>(*(ps.output_generators)) : 0),
                                         output_formatters(ps.output_formatters ? new std::list<Formatter*>(*(ps.output_formatters)) : 0),
                                         computation(ps.computation){}

PipeStep& PipeStep::operator=(const PipeStep& ps){
    if(&ps == this) return *this;

    *inputs   = *(ps.inputs);
    *outputs  = *(ps.outputs);
    *criteria = *(ps.criteria);

    *input_generators    = *(ps.input_generators);
    *output_generators   = *(ps.output_generators);
    *criteria_generators = *(ps.criteria_generators);

    *output_formatters = *(ps.output_formatters);

    computation = ps.computation;

    return *this;
}

bool PipeStep::execute() throw(bad_computation, bad_io_mapping, gestface_exception){
    if(!computation) throw bad_computation("Missing computation element");

    std::list<Generator*>::iterator in_gen_it = input_generators->begin();
    std::list<Generator*>::iterator crit_gen_it = criteria_generators->begin();
    std::list<Generator*>::iterator out_gen_it = output_generators->begin();

    std::list<input_t> tmp_inputs, tmp_criteria, tmp_outputs;

    for(; in_gen_it != input_generators->end(); in_gen_it++) add_input((*in_gen_it)->generate());
    for(; crit_gen_it != criteria_generators->end(); crit_gen_it++) add_criteria((*crit_gen_it)->generate());
    for(; out_gen_it != output_generators->end(); out_gen_it++) add_output((*out_gen_it)->generate());

    std::map<IOKey, input_t>::const_iterator in_it = inputs->begin();
    std::map<IOKey, criteria_t>::const_iterator crit_it = criteria->begin();
    std::map<IOKey, output_t>::const_iterator out_it = outputs->begin();

    for(; in_it != inputs->end(); in_it++) tmp_inputs.push_back(in_it->second);
    for(; crit_it != criteria->end(); crit_it++) tmp_criteria.push_back(crit_it->second);
    for(; out_it != outputs->end(); out_it++) tmp_outputs.push_back(out_it->second);

    UnexpectedExHandler unexpectedExHandler(throw_unexpected);

    bool computation_success = false;
    try{
        computation_success = computation->doOperation(tmp_inputs, tmp_criteria, tmp_outputs);
    }catch(boost::bad_any_cast& ex){
        throw bad_io_mapping("error while converting an IOElement to its type");
    }

    std::list<Formatter*>::iterator form_it = output_formatters->begin();
    for(; form_it != output_formatters->end(); form_it++){
        std::map<IOKey, output_t>::const_iterator out_it = outputs->find((*form_it)->get_key());
        if(out_it != outputs->end()) (*form_it)->format(*(out_it->second));
    }

    return computation_success;
}

PipeStep& PipeStep::add_input(input_t _input){
    (*inputs)[_input->getIOKey()] = _input;
    return *this;
}
PipeStep& PipeStep::add_criteria(input_t _criteria){
    (*criteria)[_criteria->getIOKey()] = _criteria;
    return *this;
}
PipeStep& PipeStep::add_output(input_t _output){
    (*outputs)[_output->getIOKey()] = _output;
    return *this;
}

PipeStep& PipeStep::add_input_generator(Generator* gen){
    input_generators->push_back(gen);
    return *this;
}
PipeStep& PipeStep::add_criteria_generator(Generator* gen){
    criteria_generators->push_back(gen);
    return *this;
}
PipeStep& PipeStep::add_output_generator(Generator* gen){
    output_generators->push_back(gen);
    return *this;
}

PipeStep& PipeStep::set_computation(PipeStepComputation* _computation){
    computation = _computation;
    return *this;
}

PipeStep& PipeStep::add_output_formatter(Formatter* formatter){
    output_formatters->push_back(formatter);
    return *this;
}
// -----------------------------------------------------------------------------

// ****** Pipeline implementation **********************************************
Pipeline::~Pipeline(){
    if(pipe_steps) delete pipe_steps;
}
Pipeline::Pipeline() : PipeStepComputation(), pipe_steps(new std::vector<PipeStep*>()){}
Pipeline::Pipeline(const Pipeline& p) : PipeStepComputation(p),
                                        pipe_steps(p.pipe_steps ? new std::vector<PipeStep*>(*(p.pipe_steps)) : 0 ){}
Pipeline& Pipeline::operator=(const Pipeline& p){
    if(&p == this) return *this;
    *pipe_steps = *(p.pipe_steps);
    return *this;
}

bool Pipeline::doOperation(const std::list<input_t>& inputs,
                           const std::list<criteria_t>& operationCriteria,
                           std::list<output_t>& outputs) throw(bad_io_mapping) {

    PipeStep* first_step = pipe_steps->at(0);
    PipeStep* last_step = pipe_steps->at(pipe_steps->size() - 1);

    std::list<input_t>::const_iterator pipe_in_it = inputs.begin();
    std::list<criteria_t>::const_iterator pipe_crit_it = operationCriteria.begin();
    std::list<output_t>::const_iterator pipe_out_it = outputs.begin();

    for(; pipe_in_it != inputs.end(); pipe_in_it++) first_step->add_input(*pipe_in_it);
    for(; pipe_crit_it != operationCriteria.end(); pipe_crit_it++) first_step->add_criteria(*pipe_crit_it);
    for(; pipe_out_it != outputs.end(); pipe_out_it++) last_step->add_output(*pipe_out_it);

    std::vector<PipeStep*>::iterator steps_it = pipe_steps->begin();
    for(; steps_it != pipe_steps->end(); steps_it++) if( !((*steps_it)->execute()) ) return false;

    return true;
}

Pipeline& Pipeline::add_pipe_step(PipeStep* pipe_step){
    pipe_steps->push_back(pipe_step);
    return *this;
}

const std::vector<IOKey>& Pipeline::getInputKeysList() const{ throw std::runtime_error("Not implemented");}
const std::vector<IOKey>& Pipeline::getCriteriaKeysList() const{throw std::runtime_error("Not implemented");}
const std::vector<IOKey>& Pipeline::getOutputKeysList() const{throw std::runtime_error("Not implemented");}
// -----------------------------------------------------------------------------

}
